/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lvneng.core;

import com.github.zengfr.easymodbus4j.ModbusConfs;
import com.github.zengfr.easymodbus4j.ModbusConsts;
import com.github.zengfr.easymodbus4j.client.ModbusClient;
import com.github.zengfr.easymodbus4j.common.util.ConsoleUtil;
import com.github.zengfr.easymodbus4j.common.util.ScheduledUtil;
import com.github.zengfr.easymodbus4j.processor.ModbusMasterResponseProcessor;
import com.github.zengfr.easymodbus4j.processor.ModbusSlaveRequestProcessor;
import com.github.zengfr.easymodbus4j.sender.ChannelSender;
import com.lvneng.processor.ExampleModbusMasterResponseProcessor;
import com.lvneng.processor.ExampleModbusSlaveRequestProcessor;
import io.netty.channel.Channel;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.util.*;

/**
 * 输入运行参数如：4,127.0.0.1,502,1,0,T,0,T,12000,heartbeat,0,54321,2000,100,F,F,T,F,127.0.0.1:502:1:1;127.0.0.1:502:2:2
 * 从左至右分别表示：type,host,port,unit_IDENTIFIER,transactionIdentifierOffset,showDebugLog,idleTimeout,autoSend,sleep,heartbeat,ignoreLengthThreshold,udpPort,maximumAddressLen,batchQuantity,coils,discreteInputs,holdingRegisters,inputRegisters,slaveList
 * type拥有创建不通角色的modbus服务器和客户端；
 * host,port主机和端口，服务器端角色只需绑定端口port，客户端角色需要连接服务器host和服务端口port；
 * unit_IDENTIFIER单元标识符；
 * transactionIdentifierOffset事务标识符偏移；
 * showDebugLog是否显示日志，T显示，F不显示；
 * idleTimeout空闲时间，默认0；
 * autoSend是否自动发送请求，T自动发送，F不自动发送；
 * sleep自动发送请求时间间隔；
 * heartbeat心跳；
 * ignoreLengthThreshold忽略长度阈值；
 * udpPort UDP端口；
 * maximumAddressLen 点表最大地址数量
 * batchQuantity 定时任务每次去点表获取的数量，batchQuantity要求 < maximumAddressLen
 * coils 是否存在线圈，F不存在，T存在
 * discreteInputs 是否存在离散输入，F不存在，T存在
 * holdingRegisters 是否存在保持寄存器，F不存在，T存在
 * inputRegisters 是否存在输入寄存器，F不存在，T存在
 * slaveList需要连接的从站地址，默认127.0.0.1:502:1:1，表示需要连接的从站的IP地址是127.0.0.1，端口是502，从站ID是1（第一个1），本程序整合几个从站后，对外开放的从站ID是1（第二个1），
 * slaveList可以是多个值，用分号;隔开。如：127.0.0.1:502:1:1;127.0.0.1:502:2:2;127.0.0.1:504:1:3;127.0.0.1:506:1:4;127.0.0.1:508:1:5，对外一共定义5个从站ID（每项最后一个数字），
 * 则其它Master连接作为服务器的Slave时候，根据host,port主机和端口和5个从站ID即可访问整合后的点表
 *
 *
 * Master(外部第三方，modbus客户端) ——————> 本程序（使用type=4，Slava作为modbus服务器端，然后本程序再启动一个Master作为modbus的客户端去访问多个Slave从站，整合数据存在在本地返回给第三方） ——————> slave1，slave2，slave3，slave4，slave5
 *
 *
 *
 * @author zengfr QQ:362505707/1163551688 Email:zengfr3000@qq.com
 *         https://github.com/zengfr/easymodbus4j
 */
public class ModbusConsoleApp {
	private static final InternalLogger logger = InternalLoggerFactory.getInstance(ModbusConsoleApp.class);
	public static ModbusSetup setup = new ModbusSetup();
	public static int maximumAddressLen = 2000;
	public static int batchQuantity = 100;
	public static boolean isCoils = false;
	public static boolean isDiscreteInputs = false;
	public static boolean isHoldingRegisters = true;
	public static boolean isInputRegisters = false;
	public static void initAndStart(String[] argsArray) throws Exception {
		ModbusConfig config = ModbusConfig.parse(argsArray);
		start(config);
	}

	public static void start(ModbusConfig cfg) throws Exception {
		ModbusConsts.DEFAULT_UNIT_IDENTIFIER = cfg.unit_IDENTIFIER;
		ModbusConsts.HEARTBEAT=cfg.heartbeat;
		
		ModbusConfs.MASTER_SHOW_DEBUG_LOG = cfg.showDebugLog;
		ModbusConfs.SLAVE_SHOW_DEBUG_LOG = cfg.showDebugLog;
		ModbusConfs.IDLE_TIMEOUT_SECOND = cfg.idleTimeOut;
		ModbusConfs.RESPONS_EFRAME_IGNORE_LENGTH_THRESHOLD= cfg.ignoreLengthThreshold;

		ModbusMasterResponseProcessor masterProcessor = new ExampleModbusMasterResponseProcessor(cfg.transactionIdentifierOffset,cfg.showDebugLog);
		ModbusSlaveRequestProcessor slaveProcessor = new ExampleModbusSlaveRequestProcessor(cfg.transactionIdentifierOffset);


		setup.initProperties();
		setup.initHandler(masterProcessor, slaveProcessor);

		maximumAddressLen = cfg.maximumAddressLen;
		batchQuantity = cfg.batchQuantity;
		isCoils = cfg.coils;
		isDiscreteInputs = cfg.discreteInputs;
		isHoldingRegisters = cfg.holdingRegisters;
		isInputRegisters = cfg.inputRegisters;

		int port = cfg.port;
		boolean autoSend = cfg.autoSend;
		String host = cfg.host;
		int sleep = cfg.sleep;
		switch (cfg.type) {

		case 6:
			setup.setupServer4RtuMaster(port);
			//sendRequests4Auto(autoSend, sleep, setup.getModbusServer().getChannels());
			break;
		case 7:
			setup.setupClient4RtuSlave(host, port);
			break;
		case 8:
			setup.setupClient4RtuMaster(host, port);
			//sendRequests4Auto(autoSend, sleep, setup.getModbusClient().getChannels());
			break;
		case 9:
			setup.setupServer4RtuSlave(port);
			break;
		case 5:
			ModbusConfs.SLAVE_SHOW_DEBUG_LOG = false;
			setup.setupServer4RtuMaster(port);
			setup.setupClient4RtuSlave(host, port);
			//sendRequests4Auto(autoSend, sleep, setup.getModbusServer().getChannels());
			break;
		case 1:
			setup.setupServer4TcpMaster(port);//Modbus Master 就作为TCP通讯的 服务器端（Server），此时，Modbus Slave 应该作为TCP通讯的 客户端（Client，对应case 2）
			//sendRequests4Auto(autoSend, sleep, setup.getModbusServer().getChannels());
			break;
		case 2:
			setup.setupClient4TcpSlave(host, port);//Modbus Slave 作为TCP通讯的 客户端（Client），此时，Modbus Master 就作为TCP通讯的 服务器端（Server，对应case 1）。
			break;
		case 3:
			setup.setupClient4TcpMaster(host, port, 1,1,1000);//Modbus Master 就作为TCP通讯的 客户端（Client），此时，Modbus Slave 应该作为TCP通讯的 服务器端（Server，对应case 4），因此，需要提供Modbus Slave的IP地址/端口（host，port）。
			//sendRequests4Auto(autoSend, sleep, setup.getModbusClient().getChannels());
			break;
		case 4:
			setup.setupServer4TcpSlave(port);//Modbus Slave 作为TCP通讯的 服务器端（Server），此时，Modbus Master 就应该作为TCP通讯的 客户端（Client，对应case 3）。
			Iterator<Integer> iterator = ModbusConfig.slaveMap.keySet().iterator();
			while (iterator.hasNext()){
				Integer externalSlaveId = iterator.next();
				IpAddress ipAddress = ModbusConfig.slaveMap.get(externalSlaveId);
				if(ipAddress != null){
					String slaveHost = ipAddress.getHost();
					int slavePort = ipAddress.getPort();
					int internalSlaveId = ipAddress.getSlaveId();
					int maximumAddressLen = ipAddress.getMaximumAddressLen();
					setup.setupClient4TcpMaster(slaveHost, slavePort, internalSlaveId, externalSlaveId, maximumAddressLen);
				}
			}
			Thread.sleep(3L *sleep);//等待Chanel初始化完成，否则Chanel == null
			sendRequests4AutoOfBatch(autoSend, sleep, setup.getClient4TcpMasterMap());
			break;
		default:
			ModbusConfs.SLAVE_SHOW_DEBUG_LOG = false;
			setup.setupServer4TcpMaster(port);//默认情况是，Modbus Master 就作为TCP通讯的 服务器端（Server），Modbus Slave 作为TCP通讯的 客户端（Client）
			setup.setupClient4TcpSlave(host, port);//默认情况，Modbus Slave 作为TCP通讯的 客户端（Client）
			//sendRequests4Auto(autoSend, sleep, setup.getModbusServer().getChannels());
			break;
		}
		Runnable runnable = () -> ConsoleUtil.clearConsole(true);
		ScheduledUtil.scheduleAtFixedRate(runnable, sleep * 5L);
	}

	protected static void sendRequests4Auto(boolean autoSend, int sleep, Collection<Channel> channels) throws InterruptedException {
		if (autoSend) {
			//ModbusMasterSchedule4ConfigFile modbusMasterAutoSender4ConfigFile = new ModbusMasterSchedule4ConfigFile();
			//modbusMasterAutoSender4ConfigFile.schedule(channels, sleep);

		}
	}

	/**
     * 自动发送请求
	 * @param autoSend
     * @param sleep
     * @param
     * @throws InterruptedException
	 */
	protected static void sendRequests4AutoOfBatch(boolean autoSend, int sleep, Map<String, MyModbusClient> client4TcpMasterMap) throws InterruptedException {
		if (autoSend) {
			Iterator<String> iterator = client4TcpMasterMap.keySet().iterator();
			while (iterator.hasNext()){
				String  slaveIdKey = iterator.next();//格式：host + ":" + port + ":" + internalSlaveId
				MyModbusClient myClient = client4TcpMasterMap.get(slaveIdKey);
				ModbusClient client = myClient.getModbusClient();
				int internalSlaveId = myClient.getInternalSlaveId();
				int externalSlaveId = myClient.getExternalSlaveId();
				int maximumAddressLen = myClient.getMaximumAddressLen();
				Channel channel =  client.getChannel();
				if(channel != null){
					AttributeKey<Integer> maximumAddressLenKey = AttributeKey.valueOf("maximumAddressLen");
					channel.attr(maximumAddressLenKey).set(maximumAddressLen);//设置该通道对应的端口modbus地址的最大地址长度
					short unitIdentifier = (short)internalSlaveId;
					ChannelSender sender = new ChannelSender(channel,unitIdentifier);
					setup.setChannelSender(externalSlaveId,sender);
					sendRequests4Auto(sender,myClient.getHost(),myClient.getPort(),myClient.getInternalSlaveId(), sleep);
				}else{
					logger.debug("sendRequests4AutoOfBatch getChannel is Error,slaveIdKey: " + slaveIdKey);
				}
			}
		}
	}

	private static void sendRequests4Auto(ChannelSender sender, String host,int port,int internalSlaveId,int sleep) {
		Runnable r = () -> {
			try{
				int localMaximumAddressLen = maximumAddressLen;
				AttributeKey<Integer> maximumAddressLenKey = AttributeKey.valueOf("maximumAddressLen");
				Attribute<Integer> maximumAddressLenAttribute = sender.getChannel().attr(maximumAddressLenKey);
				if(maximumAddressLenAttribute != null){
					int len = maximumAddressLenAttribute.get();
					if(len > 0){
						localMaximumAddressLen = len;//使用每个通道最大的地址数，防止不同通道使用了相同的最大地址 而导致部分请求越界
					}
				}
				int startingAddress = 0;
				String ipAddress = host + ":" + port + ":" + internalSlaveId;
				do {
					int batch = batchQuantity;//期望本批的获取量
					int max = startingAddress + batch;//期望 本批次获取的地址的最大值
					if(max > localMaximumAddressLen){
						//期望值过界了
						batch = localMaximumAddressLen - startingAddress;//因期望本批次获取量越界，则改变本批的获取量
					}
					if(isCoils){
						logger.debug("发送请求readCoilsAsync，ipAddress：" + ipAddress + ",startingAddress: " + startingAddress + ",quantity：" + batch);
						//sender.readCoilsAsync(startingAddress,batch);//异步请求，响应数据时候会出现异常，一半正常，一半异常
						sender.readCoils(startingAddress,batch);//同步请求
					}
					if(isDiscreteInputs){
						logger.debug("发送请求readDiscreteInputsAsync，ipAddress：" + ipAddress + ",startingAddress: " + startingAddress + ",quantity：" + batch);
						//sender.readDiscreteInputsAsync(startingAddress,batch);//异步请求，响应数据时候会出现异常，一半正常，一半异常
						sender.readDiscreteInputs(startingAddress,batch);//同步请求
					}
					if(isHoldingRegisters){
						logger.debug("发送请求readHoldingRegistersAsync，ipAddress：" + ipAddress + ",startingAddress: " + startingAddress + ",quantity：" + batch);
						//sender.readHoldingRegistersAsync(startingAddress,batch);//异步请求，响应数据时候会出现异常，一半正常，一半异常
						sender.readHoldingRegisters(startingAddress,batch);//同步请求
					}
					if(isInputRegisters){
						logger.debug("发送请求readInputRegistersAsync，ipAddress：" + ipAddress + ",startingAddress: " + startingAddress + ",quantity：" + batch);
						//sender.readInputRegistersAsync(startingAddress,batch);//异步请求，响应数据时候会出现异常，一半正常，一半异常
						sender.readInputRegisters(startingAddress,batch);//同步请求
					}
					startingAddress = startingAddress + batch;
				}while (startingAddress < localMaximumAddressLen);
			}catch (Exception e){
				e.printStackTrace();
				logger.error("sendRequests4Auto 中发送请求异常！",e);
			}
		};
		ScheduledUtil.scheduleWithFixedDelay(r, (long)sleep);
	}
}
